home *** CD-ROM | disk | FTP | other *** search
- # -*- coding: utf-8 -*-
- import gtk, cairo, pango, gobject
- import colors
- import math
- import random
- from simple_debug import simple_debug
- from gettext import gettext as _
- import sudoku
-
- TRACKER_COLORS = [
- # Use tango colors recommended here:
- # http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines
- tuple([x/255.0 for x in cols]) for cols in
- [(32,74,135), # Sky Blue 3
- (78,154,6), # Chameleon 3
- (206,92,0), # Orange 3
- (143,89,2), # Chocolate 3
- (92,53,102), # Plum 3
- (85,87,83), # Aluminium 5
- (196,160,0), # Butter 3
-
- ]
- ]
-
- def gtkcolor_to_rgb (c):
- return c.red/float(2**16),c.green/float(2**16),c.blue/float(2**16)
-
- def overlay (color_1, color_2, method=1):
- return color_1[0]+color_2[0]*method,color_1[1]+color_2[1]*method,color_1[2]+color_2[2]*method
-
- ERROR_HIGHLIGHT_COLOR = (1.0,0,0)
-
- BASE_SIZE = 35 # The "normal" size of a box (in pixels)
-
- # And the standard font-sizes -- these should fit nicely with the
- # BASE_SIZE
- BASE_FONT_SIZE = pango.SCALE * 13
- NOTE_FONT_SIZE = pango.SCALE * 6
-
- BORDER_WIDTH = 9.0 # The size of space we leave for a box
-
- BORDER_LINE_WIDTH = 4 # The size of the line we draw around a selected box
-
- LITTLE_LINE_WIDTH = 0.25
- NORMAL_LINE_WIDTH = 1 # The size of the line we draw around a box
-
- SPACING_FACTOR = 40 # The size of a box compared (roughly) to the size
- # of padding -- the larger this is, the smaller
- # the spaces
- SMALL_TO_BIG_FACTOR = 3.5 # The number of times wider than a small line a big line is.
-
- class NumberSelector (gtk.EventBox):
-
- __gsignals__ = {
- 'changed':(gobject.SIGNAL_RUN_LAST,gobject.TYPE_NONE,()),
- }
-
- def __init__ (self,default=None,upper=9):
- self.value = default
- gtk.EventBox.__init__(self)
- self.table = gtk.Table()
- self.add(self.table)
- side = int(math.sqrt(upper))
- n = 1
- for y in range(side):
- for x in range(side):
- b = gtk.Button()
- l = gtk.Label()
- if n==self.value:
- l.set_markup('<b><span size="x-small">%s</span></b>'%n)
- else:
- l.set_markup('<span size="x-small">%s</span>'%n)
- b.add(l)
- b.set_relief(gtk.RELIEF_HALF)
- l = b.get_children()[0]
- b.set_border_width(0)
- l.set_padding(0,0)
- l.get_alignment()
- b.connect('clicked',self.number_clicked,n)
- self.table.attach(b,x,x+1,y,y+1)
- n+=1
- if self.value:
- db = gtk.Button()
- l = gtk.Label()
- l.set_markup_with_mnemonic('<span size="x-small">'+_('_Clear')+'</span>')
- db.add(l); l.show()
- db.connect('clicked',self.number_clicked,0)
- self.table.attach(db,0,side,y+1,y+2)
- self.connect('key-release-event',self.key_press_cb)
- self.show_all()
-
- def key_press_cb (self, w, e):
- txt = gtk.gdk.keyval_name(e.keyval)
- if txt == 'Escape':
- self.emit('changed')
- elif txt in ['0','Delete','BackSpace']:
- self.value = None
- self.emit('changed')
- else:
- try:
- self.value = int(txt)
- except:
- print "Can't make sense of %s"%txt
- else:
- self.emit('changed')
-
- def number_clicked (self, button, n):
- self.value = n
- self.emit('changed')
-
- def get_value (self):
- return self.value
-
- def set_value (self,n):
- self.value = n
-
- class NumberBox (gtk.Widget):
-
- text = ''
- top_note_text = ''
- bottom_note_text = ''
- read_only = False
- read_only_hidden = False
- _layout = None
- _top_note_layout = None
- _bottom_note_layout = None
- text_color = None
- highlight_color = None
- custom_background_color = None
-
- __gsignals__ = {
- 'value-about-to-change':(gobject.SIGNAL_RUN_LAST,gobject.TYPE_NONE,()),
- 'changed':(gobject.SIGNAL_RUN_LAST,gobject.TYPE_NONE,()),
- # undo-change - A hacky way to handle the fact that we want to
- # respond to undo's changes but we don't want undo to respond
- # to itself...
- 'undo-change':(gobject.SIGNAL_RUN_LAST,gobject.TYPE_NONE,()),
- 'notes-changed':(gobject.SIGNAL_RUN_LAST,gobject.TYPE_NONE,()),
- }
-
- base_state = gtk.STATE_NORMAL
- number_picker_mode = False
- draw_boxes = False
-
- def __init__ (self, upper=9, text=''):
- gtk.Widget.__init__(self)
- self.upper = upper
- self.font = self.style.font_desc
- self.font.set_size(BASE_FONT_SIZE)
- self.note_font = self.font.copy()
- self.note_font.set_size(NOTE_FONT_SIZE)
- self.set_property('can-focus',True)
- self.set_property('events',gtk.gdk.ALL_EVENTS_MASK)
- self.connect('button-press-event',self.button_press_cb)
- self.connect('key-release-event',self.key_press_cb)
- self.connect('enter-notify-event',self.pointer_enter_cb)
- self.connect('leave-notify-event',self.pointer_leave_cb)
- self.connect('focus-in-event',self.focus_in_cb)
- self.connect('focus-out-event',self.focus_out_cb)
- self.connect('motion-notify-event',self.motion_notify_cb)
- self.set_text(text)
-
- def pointer_enter_cb (self, *args):
- if not self.is_focus(): self.set_state(gtk.STATE_PRELIGHT)
- def pointer_leave_cb (self, *args):
- self.set_state(self.base_state)
- self._toggle_box_drawing_(False)
-
- def focus_in_cb (self, *args):
- self.set_state(gtk.STATE_SELECTED)
- self.base_state = gtk.STATE_SELECTED
-
- def focus_out_cb (self, *args):
- self.set_state(gtk.STATE_NORMAL)
- self.base_state = gtk.STATE_NORMAL
- self.number_picker_mode = False
- if hasattr(self,'npicker') and self.npicker:
- self.npicker.destroy()
- self.npicker = None
-
- def motion_notify_cb (self, *args):
- if self.is_focus():
- self._toggle_box_drawing_(True)
- else:
- self._toggle_box_drawing_(False)
-
- def _toggle_box_drawing_ (self, val):
- if val and not self.draw_boxes:
- self.draw_boxes = True
- self.queue_draw()
- if (not val) and self.draw_boxes:
- self.draw_boxes = False
- self.queue_draw()
-
- def button_press_cb (self, w, e):
- if self.read_only: return
- if e.type == gtk.gdk._2BUTTON_PRESS:
- # ignore second click (this makes a double click in the
- # middle of a cell get us a display of the numbers, rather
- # than selecting a number.
- return
- if self.is_focus():
- x,y = e.get_coords()
- alloc = self.get_allocation()
- my_w = alloc.width
- my_h = alloc.height
- if self.number_picker_mode:
- # If we are a number picker...
- xperc = float(x)/my_w
- yperc = float(y)/my_h
- if xperc > 0.75:
- # If we're in the right quadrant, we get out of number picker mode
- self.set_text_interactive('')
- self.number_picker_mode = False
- self.queue_draw()
- return
- else:
- if xperc > 0.5: xval = 3
- elif xperc > 0.25: xval = 2
- else: xval = 1
- if yperc > 0.66: yval = 6
- elif yperc > 0.33: yval = 3
- else: yval = 0
- self.number_picker_mode = False
- self.set_text_interactive('')
- self.set_text_interactive(str(xval+yval))
- else:
- border_height = float(BORDER_WIDTH)/BASE_SIZE
- if float(y)/my_h < border_height:
- self.show_note_editor(top=True)
- elif float(y)/my_h > (1-border_height):
- self.show_note_editor(top=False)
- else:
- # In this case we're a normal old click...
- if hasattr(self,'npicker') and self.npicker:
- self.npicker.destroy()
- self.npicker = None
- self.show_number_picker()
- else:
- self.grab_focus()
-
- def key_press_cb (self, w, e):
- if self.read_only: return
- txt = gtk.gdk.keyval_name(e.keyval)
- if type(txt) == type(None):
- # Make sure we don't trigger on unplugging the A/C charger etc
- return
- txt = txt.replace('KP_', '')
- if self.get_text() == txt:
- # If there's no change, do nothing
- return
- if txt in ['0','Delete','BackSpace']:
- self.set_text_interactive('')
- elif txt in ['n','N']:
- self.show_note_editor(top=True)
- elif txt in ['m','M']:
- self.show_note_editor(top=False)
- # And then add the new value if need be
- elif txt in [str(n) for n in range(1,self.upper+1)]:
- # First do a removal event -- this is something of a
- # kludge, but it works nicely with old code that was based
- # on entries, which also behave this way (they generate 2
- # events for replacing a number with a new number - a
- # removal event and an addition event)
- if self.get_text(): self.set_text_interactive('')
- # Then add
- self.set_text_interactive(txt)
-
- def note_changed_cb (self, w, top=False):
- if top:
- self.set_note_text_interactive(top_text=w.get_text())
- else:
- self.set_note_text_interactive(bottom_text=w.get_text())
-
- def show_note_editor (self, top=True):
- alloc = self.get_allocation()
- w = gtk.Window()
- w.set_property('skip-pager-hint', True)
- w.set_property('skip-taskbar-hint', True)
- w.set_decorated(False)
- w.set_position(gtk.WIN_POS_MOUSE)
- w.set_size_request(alloc.width,alloc.height/2)
- f = gtk.Frame()
- e = gtk.Entry()
- f.add(e)
- if top: e.set_text(self.top_note_text)
- else: e.set_text(self.bottom_note_text)
- w.add(f)
- e.connect('changed', self.note_changed_cb, top)
- e.connect('focus-out-event',lambda e, ev, w: w.destroy(), w)
- e.connect('activate',lambda e, w: w.destroy(), w)
- x,y = self.window.get_origin()
- if top:
- w.move(x,y)
- else:
- w.move(x,y+int(alloc.height*0.6))
- w.show_all()
- e.grab_focus()
-
- def number_changed_cb (self, ns, w):
- w.destroy()
- self.set_text_interactive('')
- newval = ns.get_value()
- if newval:
- self.set_text_interactive(str(newval))
-
- def show_number_picker (self):
- w = gtk.Window(type=gtk.WINDOW_POPUP)
- ns = NumberSelector(upper=self.upper,default=self.get_value())
- ns.connect('changed', self.number_changed_cb, w)
- w.grab_focus()
- w.add(ns)
- r = w.get_allocation()
- my_origin = self.window.get_origin()
- x,y = self.window.get_size()
- popupx,popupy = w.get_size()
- overlapx = popupx-x
- overlapy = popupy-y
- w.move(my_origin[0]-(overlapx/2),my_origin[1]-(overlapy/2))
- w.show()
- self.npicker = w
-
- def set_text_interactive (self,text):
- self.emit('value-about-to-change')
- self.set_text(text)
- self.queue_draw()
- self.emit('changed')
-
- def set_font (self, font):
- if type(font)==str:
- font = pango.FontDescription(font)
- self.font = font
- if self.text: self.set_text(self.text)
- self.queue_draw()
-
- def set_note_font (self, font):
- if type(font)==str:
- font = pango.FontDescription(font)
- self.note_font = font
- if self.top_note_text or self.bottom_note_text:
- self.set_note_text(self.top_note_text,
- self.bottom_note_text)
- self.queue_draw()
-
- def set_text (self, text):
- self.text = text
- self._layout = self.create_pango_layout(text)
- self._layout.set_font_description(self.font)
-
- def set_notes (self, notes):
- """Hackish method to allow easy use of Undo API.
-
- Undo API requires a set method that is called with one
- argument (the result of a get method)"""
- self.set_note_text(top_text=notes[0],
- bottom_text=notes[1])
- self.queue_draw()
-
- def set_note_text (self, top_text=None,bottom_text=None):
- if top_text is not None:
- self.top_note_text = top_text
- self._top_note_layout = self.create_pango_layout(top_text)
- self._top_note_layout.set_font_description(self.note_font)
- if bottom_text is not None:
- self.bottom_note_text = bottom_text
- self._bottom_note_layout = self.create_pango_layout(bottom_text)
- self._bottom_note_layout.set_font_description(self.note_font)
- self.queue_draw()
-
- def set_note_text_interactive (self, *args, **kwargs):
- self.emit('value-about-to-change')
- self.set_note_text(*args,**kwargs)
- self.emit('notes-changed')
-
- def do_realize (self):
- # The do_realize method is responsible for creating GDK (windowing system)
- # resources. In this example we will create a new gdk.Window which we
- # then draw on
-
- # First set an internal flag telling that we're realized
- self.set_flags(self.flags() | gtk.REALIZED)
-
- # Create a new gdk.Window which we can draw on.
- # Also say that we want to receive exposure events by setting
- # the event_mask
- self.window = gtk.gdk.Window(
- self.get_parent_window(),
- width=self.allocation.width,
- height=self.allocation.height,
- window_type=gtk.gdk.WINDOW_CHILD,
- wclass=gtk.gdk.INPUT_OUTPUT,
- event_mask=self.get_events() | gtk.gdk.EXPOSURE_MASK)
-
- # Associate the gdk.Window with ourselves, Gtk+ needs a reference
- # between the widget and the gdk window
- self.window.set_user_data(self)
-
- # Attach the style to the gdk.Window, a style contains colors and
- # GC contextes used for drawing
- self.style.attach(self.window)
-
- # The default color of the background should be what
- # the style (theme engine) tells us.
- self.style.set_background(self.window, gtk.STATE_NORMAL)
- self.window.move_resize(*self.allocation)
-
- def do_unrealize (self):
- # The do_unrealized method is responsible for freeing the GDK resources
-
- # De-associate the window we created in do_realize with ourselves
- self.window.set_user_data(None)
-
- def do_size_request (self, requisition):
- # The do_size_request method Gtk+ is calling on a widget to ask
- # it the widget how large it wishes to be. It's not guaranteed
- # that gtk+ will actually give this size to the widget
-
- # In this case, we say that we want to be as big as the
- # text is, and a square
- width, height = self._layout.get_size()
- if width > height:
- side = width/pango.SCALE
- else:
- side = height/pango.SCALE
- requisition.width = side; requisition.height = side
-
- def do_size_allocate(self, allocation):
- # The do_size_allocate is called by when the actual size is known
- # and the widget is told how much space could actually be allocated
-
- # Save the allocated space
- self.allocation = allocation
-
- # If we're realized, move and resize the window to the
- # requested coordinates/positions
- if self.flags() & gtk.REALIZED:
- self.window.move_resize(*allocation)
-
- def do_expose_event(self, event):
- # The do_expose_event is called when the widget is asked to draw itself
- # Remember that this will be called a lot of times, so it's usually
- # a good idea to write this code as optimized as it can be, don't
- # Create any resources in here.
- x, y, w, h = self.allocation
- cr = self.window.cairo_create()
- if h<w:
- scale = h/float(BASE_SIZE)
- else:
- scale = w/float(BASE_SIZE)
- cr.scale(scale,scale)
- self.draw_background_color(cr)
- if self.number_picker_mode:
- self.draw_numbers(cr)
- return
- if self.is_focus():
- self.draw_highlight_box(cr)
- self.draw_normal_box(cr)
- self.draw_text(cr)
- if self.draw_boxes and self.is_focus():
- self.draw_note_area_highlight_box(cr)
-
-
- def draw_background_color (self, cr):
- if self.read_only:
- if self.custom_background_color:
- r,g,b = self.custom_background_color
- cr.set_source_rgb(
- r*0.6,g*0.6,b*0.6
- )
- else:
- cr.set_source_color(self.style.base[gtk.STATE_INSENSITIVE])
- elif self.is_focus():
- cr.set_source_color(self.style.base[gtk.STATE_SELECTED])
- elif self.custom_background_color:
- cr.set_source_rgb(*self.custom_background_color)
- else:
- cr.set_source_color(
- self.style.base[self.state]
- )
- cr.rectangle(
- 0,0,BASE_SIZE,BASE_SIZE
- )
- cr.fill()
-
- def draw_normal_box (self, cr):
- state = self.state
- if state == gtk.STATE_SELECTED:
- # When the widget is selected, we still want the outer box to look normal
- state = gtk.STATE_NORMAL
- cr.set_source_color(
- self.style.mid[state]
- )
- cr.rectangle(
- NORMAL_LINE_WIDTH*0.5,
- NORMAL_LINE_WIDTH*0.5,
- BASE_SIZE-NORMAL_LINE_WIDTH,
- BASE_SIZE-NORMAL_LINE_WIDTH,
- )
- cr.set_line_width(NORMAL_LINE_WIDTH)
- cr.set_line_join(cairo.LINE_JOIN_ROUND)
- cr.stroke()
- # And now draw a thinner line around the very outside...
- cr.set_source_color(
- self.style.dark[state]
- )
- cr.rectangle(
- NORMAL_LINE_WIDTH*0.25,
- NORMAL_LINE_WIDTH*0.25,
- BASE_SIZE-NORMAL_LINE_WIDTH*0.5,
- BASE_SIZE-NORMAL_LINE_WIDTH*0.5,
- )
- cr.set_line_width(NORMAL_LINE_WIDTH*0.5)
- cr.set_line_join(cairo.LINE_JOIN_MITER)
- cr.stroke()
-
- def draw_highlight_box (self,cr):
- cr.set_source_color(
- self.style.base[gtk.STATE_SELECTED]
- )
- cr.rectangle(
- # left-top
- BORDER_LINE_WIDTH*0.5,
- BORDER_LINE_WIDTH*0.5,
- # bottom-right
- BASE_SIZE-(BORDER_LINE_WIDTH),
- BASE_SIZE-(BORDER_LINE_WIDTH),
- )
- cr.set_line_width(BORDER_LINE_WIDTH)
- cr.set_line_join(cairo.LINE_JOIN_ROUND)
- cr.stroke()
-
- def draw_note_area_highlight_box (self, cr):
- # set up our paint brush...
- cr.set_source_color(
- self.style.mid[self.state]
- )
- cr.set_line_width(NORMAL_LINE_WIDTH)
- cr.set_line_join(cairo.LINE_JOIN_ROUND)
- # top rectangle
- cr.rectangle(NORMAL_LINE_WIDTH*0.5,
- NORMAL_LINE_WIDTH*0.5,
- BASE_SIZE-NORMAL_LINE_WIDTH,
- BORDER_WIDTH-NORMAL_LINE_WIDTH)
- cr.stroke()
- # bottom rectangle
- cr.rectangle(NORMAL_LINE_WIDTH*0.5,#x
- BASE_SIZE - BORDER_WIDTH-(NORMAL_LINE_WIDTH*0.5),#y
- BASE_SIZE-NORMAL_LINE_WIDTH,#x2
- BASE_SIZE-NORMAL_LINE_WIDTH #y2
- )
- cr.stroke()
-
- def draw_text (self, cr):
- if self.text_color:
- cr.set_source_rgb(*self.text_color)
- elif self.read_only:
- cr.set_source_color(self.style.text[gtk.STATE_NORMAL])
- else:
- cr.set_source_color(self.style.text[self.state])
- # And draw the text in the middle of the allocated space
- if self._layout:
- fontw, fonth = self._layout.get_pixel_size()
- cr.move_to(
- (BASE_SIZE/2)-(fontw/2),
- (BASE_SIZE/2) - (fonth/2),
- )
- cr.update_layout(self._layout)
- cr.show_layout(self._layout)
- cr.set_source_color(self.style.text[self.state])
- # And draw any note text...
- if self._top_note_layout:
- fontw, fonth = self._top_note_layout.get_pixel_size()
- cr.move_to(
- NORMAL_LINE_WIDTH,
- 0,
- )
- cr.update_layout(self._top_note_layout)
- cr.show_layout(self._top_note_layout)
- if self._bottom_note_layout:
- fontw, fonth = self._bottom_note_layout.get_pixel_size()
- cr.move_to(
- NORMAL_LINE_WIDTH,
- BASE_SIZE-fonth,
- )
- cr.update_layout(self._bottom_note_layout)
- cr.show_layout(self._bottom_note_layout)
-
- def draw_numbers (self, cr):
- if not hasattr(self,'number_text'):
- self.small_digit_height = 1
- self.small_digit_width = 1
- self.number_text = []
- for n in range(self.upper): # + ['X']:
- if type(n)==int:
- n = str(n+1)
- txt = self.create_pango_layout(n)
- txt.set_font_description(self.note_font)
- if not hasattr(self,'bold_note_font'):
- self.bold_note_font = self.note_font.copy()
- self.bold_note_font.set_weight(pango.WEIGHT_BOLD)
- bold_txt = self.create_pango_layout(n)
- bold_txt.set_font_description(self.bold_note_font)
- self.number_text.append((txt,bold_txt))
- w,h = bold_txt.get_pixel_size()
- if w > self.small_digit_width: self.small_digit_width = w*1.2
- if h > self.small_digit_height: self.small_digit_height = h
- val = self.get_value()
- cols = (BASE_SIZE-NORMAL_LINE_WIDTH*2) / self.small_digit_width
- rows = (BASE_SIZE-NORMAL_LINE_WIDTH*2) / self.small_digit_height
- cols = 3; rows=3
- row_size = BASE_SIZE/rows
- col_size = BASE_SIZE/cols
- n = 0
- for y in range(rows):
- for x in range(cols):
- if y < 2 and x > 2: continue
- if n >= len(self.number_text):
- break
- txt = self.number_text[n]
- if val==(n+1):
- layout = txt[1] # grab bold layout
- else:
- layout = txt[0] # grab normal layout
- w,h = layout.get_pixel_size()
- xpadding = (col_size - w)/2
- ypadding = (row_size - h)/2
- # draw little boxes...
- cr.set_source_color(gtk.gdk.Color(2**16,0,0))
- cr.set_line_width(LITTLE_LINE_WIDTH)
- cr.rectangle(LITTLE_LINE_WIDTH*0.5+NORMAL_LINE_WIDTH*0.5+(x*col_size),
- LITTLE_LINE_WIDTH*0.5+NORMAL_LINE_WIDTH*0.5+(y*row_size),
- LITTLE_LINE_WIDTH*0.5+NORMAL_LINE_WIDTH*0.5+((x+1)*col_size),
- LITTLE_LINE_WIDTH*0.5+NORMAL_LINE_WIDTH*0.5+((y+1)*row_size),
- )
- cr.stroke()
- cr.set_source_color(self.style.text[self.state])
- cr.move_to(NORMAL_LINE_WIDTH+(x*col_size)+xpadding,
- NORMAL_LINE_WIDTH+(y*row_size)+ypadding)
- cr.update_layout(layout)
- cr.show_layout(layout)
- n+=1
-
- def set_text_color (self, color):
- self.text_color = color
- self.queue_draw()
-
- def set_background_color (self, color):
- self.custom_background_color = color
- self.queue_draw()
-
- def hide_notes (self):
- pass
-
- def show_notes (self):
- pass
-
- def set_value_from_undo (self, v):
- self.set_value(v)
- self.emit('undo_change')
-
- def set_value (self,v):
- if 0 < v <= self.upper:
- self.set_text(str(v))
- else:
- self.set_text('')
- self.queue_draw()
-
- def get_value (self):
- try: return int(self.text)
- except: return None
-
- def get_text (self): return self.text
- def get_note_text (self): return self.top_note_text,self.bottom_note_text
-
- class SudokuNumberBox (NumberBox):
-
- normal_color = None
- highlight_color = ERROR_HIGHLIGHT_COLOR
-
- def set_color (self, color):
- self.normal_color = color
- self.set_text_color(self.normal_color)
-
- def unset_color (self): self.set_color(None)
-
- def set_error_highlight (self, val):
- if val:
- self.set_text_color((1.0,0,0))
- else:
- self.set_text_color(self.normal_color)
-
- def set_read_only (self, val):
- self.read_only = val
- if not hasattr(self,'bold_font'):
- self.normal_font = self.font
- self.bold_font = self.font.copy()
- self.bold_font.set_weight(pango.WEIGHT_BOLD)
- if self.read_only:
- self.set_font(self.bold_font)
- else:
- self.set_font(self.normal_font)
- self.queue_draw()
-
- def set_impossible (self, val):
- if val: self.set_text('X')
- else: self.set_text('')
-
-
- gobject.type_register(NumberBox)
-
- class SudokuNumberGrid (gtk.AspectFrame):
-
- def __init__ (self, group_size=9):
- self.table = gtk.Table(rows=group_size,columns=group_size,homogeneous=True)
- self.group_size = group_size
- self.__entries__ = {}
- for x in range(self.group_size):
- for y in range(self.group_size):
- e = SudokuNumberBox(upper=self.group_size)
- e.x = x
- e.y = y
- self.table.attach(e,x,x+1,y,y+1,
- )
- self.__entries__[(x,y)] = e
- gtk.AspectFrame.__init__(self,obey_child=False)
- self.set_shadow_type(gtk.SHADOW_NONE)
- self.eb = gtk.EventBox()
- self.eb.add(self.table)
- self.add(self.eb)
- self.connect('size-allocate',self.allocate_cb)
- self.show_all()
-
- def allocate_cb (self, w, rect):
- if rect.width > rect.height: side = rect.height
- else: side = rect.width
- # we want our small spacing to be 1/15th the size of a box
- spacing = float(side) / (self.group_size * SPACING_FACTOR)
- if spacing == 0: spacing = 1
- if hasattr(self,'small_spacing') and spacing == self.small_spacing:
- return
- else:
- self.change_spacing(spacing)
-
- def change_spacing (self, small_spacing):
- self.small_spacing = small_spacing
- self.big_spacing = int(small_spacing*SMALL_TO_BIG_FACTOR)
- self.table.set_row_spacings(int(small_spacing))
- self.table.set_col_spacings(int(small_spacing))
- box_side = int(math.sqrt(self.group_size))
- for n in range(1,box_side):
- self.table.set_row_spacing(box_side*n-1,self.big_spacing)
- self.table.set_col_spacing(box_side*n-1,self.big_spacing)
- self.table.set_border_width(self.big_spacing)
-
- def get_focused_entry (self):
- for e in self.__entries__.values():
- if e.is_focus():
- return e
-
- def set_bg_color (self, color):
- if type(color)==str:
- try: color = gtk.gdk.color_parse(color)
- except:
- print 'set_bg_color handed Bad color',color
- return
- self.eb.modify_bg(gtk.STATE_NORMAL,color)
- self.eb.modify_base(gtk.STATE_NORMAL,color)
- self.eb.modify_fg(gtk.STATE_NORMAL,color)
- self.table.modify_bg(gtk.STATE_NORMAL,color)
- self.table.modify_base(gtk.STATE_NORMAL,color)
- self.table.modify_fg(gtk.STATE_NORMAL,color)
- for e in self.__entries__.values():
- e.modify_bg(gtk.STATE_NORMAL,color)
-
- class ParallelDict (dict):
- """A handy new sort of dictionary for tracking conflicts.
-
- pd = ParallelDict()
- pd[1] = [2,3,4] # 1 is linked with 2,3 and 4
- pd -> {1:[2,3,4],2:[1],3:[1],4:[1]}
- pd[2] = [1,3,4] # 2 is linked with 3 and 4 as well as 1
- pd -> {1: [2,3,4],2:[3,4],3:[1,2],4:[1,2]}
- Now for the cool part...
- del pd[1]
- pd -> {2: [2,3],3:[2],4:[2]}
-
- Pretty neat, no?
- """
- def __init__ (self, *args):
- dict.__init__(self,*args)
-
- def __setitem__ (self, k, v):
- dict.__setitem__(self,k,set(v))
- for i in v:
- if i == k: continue
- if self.has_key(i):
- self[i].add(k)
- else:
- dict.__setitem__(self,i,set([k]))
-
- def __delitem__ (self, k):
- v=self[k]
- dict.__delitem__(self,k)
- for i in v:
- if i==k: continue
- if self.has_key(i):
- # Make sure we have a reference to i. If we don't
- # something has gone wrong... but according to bug
- # 385937 this has gone wrong at least once, so we'd
- # better check for it.
- if k in self[i]: self[i].remove(k)
- if not self[i]:
- # If k was the last value in the list of values
- # for i, then we delete i from our dictionary
- dict.__delitem__(self,i)
-
- class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
-
- __gsignals__ = {
- 'focus-changed':(gobject.SIGNAL_RUN_LAST,gobject.TYPE_NONE,()),
- 'puzzle-finished':(gobject.SIGNAL_RUN_LAST,gobject.TYPE_NONE,())
- }
-
- do_highlight_cells = False
-
- @simple_debug
- def __init__ (self,grid=None,group_size=9,
- show_impossible_implications=False):
- group_size=int(group_size)
- self.hints = 0
- self.always_show_hints = False
- self.auto_fills = 0
- self.show_impossible_implications = show_impossible_implications
- self.impossible_hints = 0
- self.impossibilities = []
- self.trackers = {}
- self.__trackers_tracking__ = {}
- self.__colors_used__ = [None,ERROR_HIGHLIGHT_COLOR]
- gobject.GObject.__init__(self)
- SudokuNumberGrid.__init__(self,group_size=group_size)
- self.setup_grid(grid,group_size)
- for e in self.__entries__.values():
- e.show()
- e.connect('undo-change',self.entry_callback)
- e.connect('changed',self.entry_callback)
- e.connect('focus-in-event',self.focus_callback)
- self.connect('focus-changed',self.highlight_cells)
-
- @simple_debug
- def focus_callback (self, e, event):
- self.focused = e
- self.emit('focus-changed')
-
- def get_highlight_colors (self):
- entry = self.__entries__.values()[0]
- default_color = gtkcolor_to_rgb(entry.style.bg[gtk.STATE_SELECTED])
- hsv = colors.rgb_to_hsv(*default_color)
- box_s = hsv[1]
- box_v = hsv[2]
- if box_v < 0.5: box_v = box_v*2
- if box_s > 0.75:
- box_s = box_s*0.5
- else:
- box_s = box_s*1.5
- if box_s > 1: box_s = 1.0
- self.box_color = colors.hsv_to_rgb(
- hsv[0],box_s,box_v
- )
- self.box_and_row_color = colors.rotate_hue_rgb(*self.box_color,**{'rotate_by':0.33/2})
- self.row_color = colors.rotate_hue_rgb(*self.box_color,**{'rotate_by':0.33})
- self.col_color = colors.rotate_hue_rgb(*self.box_color,**{'rotate_by':0.66})
- self.box_and_col_color = colors.rotate_hue_rgb(*self.box_color,**{'rotate_by':1.0-(0.33/2)})
-
- def toggle_highlight (self, val):
- self.do_highlight_cells = val
- self.unhighlight_cells()
- if hasattr(self,'focused') and self.focused: self.highlight_cells()
-
- def unhighlight_cells (self, *args):
- for e in self.__entries__.values(): e.set_background_color(None)
-
- def highlight_cells (self, *args):
- if not self.do_highlight_cells: return
- self.unhighlight_cells()
- if not hasattr(self,'box_color'): self.get_highlight_colors()
- my_x,my_y = self.focused.x,self.focused.y
-
- # col_coords can sometimes be null.
- if not hasattr(self.grid, 'col_coords'): return
-
- for x,y in self.grid.col_coords[my_x]:
- if (x,y) != (my_x,my_y):
- self.__entries__[(x,y)].set_background_color(self.col_color)
- for x,y in self.grid.row_coords[my_y]:
- if (x,y) != (my_x,my_y):
- self.__entries__[(x,y)].set_background_color(self.row_color)
- for x,y in self.grid.box_coords[self.grid.box_by_coords[(my_x,my_y)]]:
- if (x,y) != (my_x,my_y):
- e = self.__entries__[(x,y)]
- if x==my_x:
- e.set_background_color(self.box_and_col_color)
- elif y==my_y:
- e.set_background_color(self.box_and_row_color)
- else:
- e.set_background_color(self.box_color)
-
-
- @simple_debug
- def show_hint (self):
- if hasattr(self,'focused'):
- entry = self.focused
- if entry.read_only or entry.get_text():
- pass
- else:
- self.show_hint_for_entry(entry,interactive=True)
-
- def show_hint_for_entry (self, entry, interactive=False):
- if interactive:
- set_method = entry.set_note_text_interactive
- else:
- set_method = entry.set_note_text
- vals=self.grid.possible_values(entry.x,entry.y)
- vals = list(vals)
- vals.sort()
- if vals:
- ''.join([str(v) for v in vals])
- txt = ''.join([str(v) for v in vals])
- if txt != entry.get_text():
- set_method(bottom_text=txt)
- self.hints += 1
- elif not entry.get_text():
- if entry.get_text() != 'X':
- self.hints += 1
- set_method(bottom_text='X')
- else:
- set_method(bottom_text="")
-
- @simple_debug
- def num_to_str (self, n):
- if n >= 10: return SuperNumberEntry.conversions[n]
- else: return str(n)
-
- @simple_debug
- def reset_grid (self):
- """Reset grid to its original setup.
-
- Return a list of items we removed so that callers can handle
- e.g. Undo properly"""
- removed = []
- for x in range(self.group_size):
- for y in range(self.group_size):
- if not self.grid.virgin._get_(x,y):
- val = self.__entries__[(x,y)].get_value() # get the value from the user-visible grid,
- if val:
- removed.append((x,y,val,self.trackers_for_point(x,y,val)))
- self.remove(x,y,do_removal=True)
- return removed
-
- def clear_notes (self, clear_args={'top_text':'','bottom_text':''}):
- """Remove all notes."""
- self.removed = []
- for x in range(self.group_size):
- for y in range(self.group_size):
- e = self.__entries__[(x,y)]
- top,bottom = e.get_note_text()
- if top or bottom:
- self.removed.append((x,y,(top,bottom)))
- e.set_note_text(**clear_args)
- e.queue_draw()
- return self.removed
-
- def clear_hints (self):
- self.clear_notes(clear_args={'bottom_text':''})
-
- @simple_debug
- def blank_grid (self):
- for x in range(self.group_size):
- for y in range(self.group_size):
- self.remove(x,y)
- e=self.__entries__[(x,y)]
- e.set_read_only(False)
- self.grid = None
- self.clear_notes()
-
- @simple_debug
- def change_grid (self, grid, group_size):
- self.auto_fills = 0
- self.hints = 0
- self.impossible_hints = 0
- self.trackers = {}
- self.__trackers_tracking__ = {}
- self.__colors_used__ = [None,ERROR_HIGHLIGHT_COLOR]
- self.blank_grid()
- self.setup_grid(grid,group_size)
-
- @simple_debug
- def load_game (self, game):
- """Load a game.
-
- A game is simply a two lined string where the first line represents our
- virgin self and line two represents our game-in-progress.
- """
- self.blank_grid()
- if '\n' in game:
- virgin,in_prog = game.split('\n')
- else:
- virgin = game; in_prog = ''
- group_size=int(math.sqrt(len(virgin.split())))
- self.change_grid(virgin,group_size=group_size)
- # This int() will break if we go to 16x16 grids...
- if in_prog:
- values = [int(c) for c in in_prog.split()]
- for row in range(group_size):
- for col in range(group_size):
- index = row * 9 + col
- if values[index] and not self.grid._get_(col,row):
- self.add_value_to_ui(col,row,values[index])
-
- @simple_debug
- def setup_grid (self, grid, group_size):
- self.doing_initial_setup = True
- self.__error_pairs__ = ParallelDict()
- if isinstance(grid,sudoku.SudokuGrid):
- self.grid = sudoku.InteractiveSudoku(grid.grid,group_size=grid.group_size)
- else:
- self.grid = sudoku.InteractiveSudoku(grid,group_size=group_size)
- for x in range(group_size):
- for y in range(group_size):
- val=self.grid._get_(x,y)
- if val: self.add_value(x,y,val)
- self.doing_initial_setup = False
-
- @simple_debug
- def entry_callback (self, widget, *args):
- if not widget.get_text():
- if self.grid and self.grid._get_(widget.x,widget.y):
- self.grid.remove(widget.x,widget.y)
- self.remove(widget.x,widget.y)
- else:
- self.entry_validate(widget)
- if self.show_impossible_implications:
- self.mark_impossible_implications(widget.x,widget.y)
- if self.always_show_hints:
- self.update_all_hints()
-
- def update_all_hints (self):
- for x in range(self.group_size):
- for y in range(self.group_size):
- e = self.__entries__[(x,y)]
- if e.read_only:
- pass
- elif e.get_text():
- e.set_note_text(bottom_text='')
- else:
- self.show_hint_for_entry(e)
-
- @simple_debug
- def entry_validate (self, widget, *args):
- val = widget.get_value()
- try:
- self.add_value(widget.x,widget.y,val)
- if self.grid.check_for_completeness():
- self.emit('puzzle-finished')
- except sudoku.ConflictError, err:
- conflicts=self.grid.find_conflicts(err.x,err.y,err.value)
- for conflict in conflicts:
- widget.set_error_highlight(True)
- self.__entries__[conflict].set_error_highlight(True)
- self.__error_pairs__[(err.x,err.y)]=conflicts
-
- def add_value_to_ui (self, x, y, val, trackers=[]):
- """Add our value back to our grid come hell or high water.
-
- We add our value -- if there is an error, we make the value
- appear as if the user had typed it; i.e. it will show up with
- error highlighting."""
- try:
- self.add_value(x, y, val, trackers=[])
- except sudoku.ConflictError:
- self.entry_validate(self.__entries__[(x,y)])
-
- @simple_debug
- def add_value (self, x, y, val, trackers=[]):
- """Add value val at position x,y.
-
- If tracker is True, we track it with tracker ID tracker.
-
- Otherwise, we use any currently tracking trackers to track our addition.
-
- Providing the tracker arg is mostly useful for e.g. undo/redo
- or removed items.
-
- To specify NO trackers, use trackers=[-1]
- """
- # Add the value to the UI to display
- self.__entries__[(x,y)].set_value(val)
- if self.doing_initial_setup:
- self.__entries__[(x,y)].set_read_only(True)
- # Handle any trackers.
- if trackers:
- # Explicitly specified tracker
- for tracker in trackers:
- if tracker==-1: pass
- self.__entries__[(x,y)].set_color(self.get_tracker_color(tracker))
- self.trackers[tracker].append((x,y,val))
- elif True in self.__trackers_tracking__.values():
- for k,v in self.__trackers_tracking__.items():
- if v:
- self.__entries__[(x,y)].set_color(self.get_tracker_color(k))
- self.trackers[k].append((x,y,val))
- # Add value to our underlying sudoku grid -- this will raise
- # an error if the value is out of bounds with the current
- # rules.
- self.grid.add(x,y,val,True)
- # Draw our entry
- self.__entries__[(x,y)].queue_draw()
-
- @simple_debug
- def remove (self, x, y, do_removal=False):
- """Remove x,y from our visible grid.
-
- If do_removal, remove it from our underlying grid as well.
- """
- e=self.__entries__[(x,y)]
- if do_removal and self.grid and self.grid._get_(x,y):
- self.grid.remove(x,y)
- if self.__error_pairs__.has_key((x,y)):
- e.set_error_highlight(False)
- errors_removed = self.__error_pairs__[(x,y)]
- del self.__error_pairs__[(x,y)]
- for coord in errors_removed:
- # If we're not an error by some other pairing...
- if not self.__error_pairs__.has_key(coord):
- linked_entry = self.__entries__[coord]
- linked_entry.set_error_highlight(False)
- # Its possible this highlighted error was never
- # added to our internal grid, in which case we'd
- # better make sure it is...
- if self.grid and not self.grid._get_(linked_entry.x,linked_entry.y):
- # entry_validate will add the value to our
- # internal grid if there are no other
- # conflicts
- self.entry_validate(linked_entry)
- # remove trackers
- for t in self.trackers_for_point(x,y):
- remove = []
- for crumb in self.trackers[t]:
- if crumb[0]==x and crumb[1]==y:
- remove.append(crumb)
- for r in remove:
- self.trackers[t].remove(r)
- if e.get_text(): e.set_value(0)
- e.unset_color()
-
- @simple_debug
- def auto_fill (self):
- changed=self.grid.auto_fill()
- retval = []
- for coords,val in changed:
- self.add_value(coords[0],coords[1],val)
- retval.append((coords[0],coords[1],val))
- if self.show_impossible_implications:
- self.mark_impossible_implications(*coords)
- if retval: self.auto_fills += 1
- if self.grid.check_for_completeness():
- self.emit('puzzle-finished')
- return retval
-
- @simple_debug
- def auto_fill_current_entry (self):
- e = self.get_focused_entry()
- if not e: return
- filled = self.grid.auto_fill_for_xy(e.x,e.y)
- if filled and filled!=-1:
- e.set_text_interactive('')
- e.set_text_interactive(str(filled[1]))
-
- @simple_debug
- def mark_impossible_implications (self, x, y):
- if not self.grid: return
- implications = self.grid.find_impossible_implications(x,y)
- if implications:
- for x,y in implications:
- self.__entries__[(x,y)].set_impossible(True)
- if not (x,y) in self.impossibilities:
- self.impossible_hints += 1
- for x,y in self.impossibilities:
- if not (x,y) in implications:
- self.__entries__[(x,y)].set_impossible(False)
- self.impossibilities = implications
-
- @simple_debug
- def create_tracker (self, identifier=0):
- if not identifier: identifier = 0
- while self.trackers.has_key(identifier): identifier+=1
- self.trackers[identifier]=[]
- return identifier
-
- def trackers_for_point (self, x, y, val=None):
- if val:
- # if we have a value we can do this a simpler way...
- track_for_point = filter(
- lambda t: (x,y,val) in t[1],
- self.trackers.items()
- )
- else:
- track_for_point = filter(
- lambda tkr: True in [t[0]==x and t[1]==y for t in tkr[1]],
- self.trackers.items())
- return [t[0] for t in track_for_point]
-
- def get_tracker_color (self, identifier):
- if len(TRACKER_COLORS)>identifier:
- return TRACKER_COLORS[identifier]
- else:
- random_color = TRACKER_COLORS[0]
- while random_color in TRACKER_COLORS:
- # If we have generated all possible colors, this will
- # enter an infinite loop
- random_color = (random.randint(0,100)/100.0,
- random.randint(0,100)/100.0,
- random.randint(0,100)/100.0)
- TRACKER_COLORS.append(random_color)
- return self.get_tracker_color(identifier)
-
- @simple_debug
- def toggle_tracker (self, identifier, value):
- """Toggle tracking for tracker identified by identifier."""
- self.__trackers_tracking__[identifier]=value
-
- def delete_by_tracker (self, identifier):
- """Delete all cells tracked by tracker ID identifer."""
- ret = []
- while self.trackers[identifier]:
- x,y,v = self.trackers[identifier][0]
- ret.append((x,y,v,self.trackers_for_point(x,y,v)))
- self.remove(x,y)
- if self.grid and self.grid._get_(x,y):
- self.grid.remove(x,y)
- return ret
-
- def delete_except_for_tracker (self, identifier):
- tracks = self.trackers[identifier]
- removed = []
- for x in range(self.group_size):
- for y in range(self.group_size):
- val = self.grid._get_(x,y)
- if (val
- and (x,y,val) not in tracks
- and not self.grid.virgin._get_(x,y)
- ):
- removed.append((x,y,val,self.trackers_for_point(x,y,val)))
- self.remove(x,y)
- if self.grid and self.grid._get_(x,y):
- self.grid.remove(x,y)
-
- return removed
-
- def add_tracker (self, x, y, tracker, val=None):
- self.__entries__[(x,y)].set_color(self.get_tracker_color(tracker))
- if not val: val = self.grid._get_(x,y)
- self.trackers[tracker].append((x,y,val))
-
- def remove_tracker (self, x, y, tracker, val=None):
- if not val: val = self.grid._get_(x,y)
- self.trackers[tracker].remove((x,y,val))
-
- class GridDancer:
-
- DANCE_COLORS = [colors.color_hex_to_float(hx) for hx in
- [
- '#cc0000', # red
- '#ef2929',
- '#f57900', # orange
- '#fcaf3e',
- '#fce94f',
- '#8ae234', # green
- '#73d216',
- '#729fcf', # blue
- '#3465a4',
- '#ad7fa8', # violet
- '#75507b', ]
- ]
-
- STEPS_PER_ANIMATION = 10
-
- def __init__ (self, grid):
- self.animations = [self.value_dance,
- self.box_dance,
- self.col_dance,
- self.row_dance,]
- self.current_animation = self.value_dance
- self.step = 0
- self.grid = grid
- self.dancing = False
- for box in self.grid.__entries__.values():
- if box.read_only == True:
- box.read_only = False
- box.read_only_hidden = True
- box.queue_draw()
-
- def start_dancing (self):
- for box in self.grid.__entries__.values():
- box.props.can_focus = False
- self.grid.get_toplevel().child_focus(gtk.DIR_TAB_BACKWARD)
- self.dancing = True
- gobject.timeout_add(350,self.dance_grid)
-
- def stop_dancing (self):
- self.dancing = False
- for box in self.grid.__entries__.values():
- box.props.can_focus = True
- if box.read_only_hidden == True:
- box.read_only = True
- box.read_only_hidden = False
- self.grid.unhighlight_cells()
-
- def do_dance_step (self):
- self.grid.__entries__[(random.randint(0,8),
- random.randint(0,8))
- ].set_background_color(
- random.choice(self.DANCE_COLORS)
- )
-
- def rotate_animation (self):
- ci = self.animations.index(self.current_animation)
- if (ci+1) == len(self.animations):
- ni = 0
- else:
- ni = ci + 1
- self.current_animation = self.animations[ni]
- self.step = 0
-
- def dance_grid (self):
- if not self.dancing: return
- if self.step > self.STEPS_PER_ANIMATION:
- self.rotate_animation()
- if hasattr(self,'adjustment'):
- self.adjustment += 1
- else:
- self.adjustment = 0
- if self.adjustment >= 9: self.adjustment = 0
- try:
- self.current_animation()
- except AttributeError:
- return True
- self.step += 1
- if self.dancing:
- return True
-
- def col_dance (self):
- for x in range(9):
- n = (x + self.adjustment) % len(self.DANCE_COLORS)
- color = self.DANCE_COLORS[n]
- for y in range(9):
- self.grid.__entries__[(x,y)].set_background_color(color)
-
- def row_dance (self):
- for y in range(9):
- n = (y + self.adjustment) % len(self.DANCE_COLORS)
- color = self.DANCE_COLORS[n]
- for x in range(9):
- self.grid.__entries__[(x,y)].set_background_color(color)
-
- def box_dance (self):
- for box in range(9):
- n = (box + self.adjustment) % len(self.DANCE_COLORS)
- color = self.DANCE_COLORS[n]
- for x,y in self.grid.grid.box_coords[box]:
- self.grid.__entries__[(x,y)].set_background_color(color)
-
- def value_dance (self):
- for value in range(10):
- n = (value + self.adjustment) % len(self.DANCE_COLORS)
- color = self.DANCE_COLORS[n]
- for x in range(9):
- for y in range(9):
- if self.grid.grid._get_(x,y)==value:
- self.grid.__entries__[(x,y)].set_background_color(color)
-
- def test_dance_grid (grid):
- dancer = GridDancer(grid)
- dancer.start_dancing()
- def stop (*args): dancer.stop_dancing()
- gobject.timeout_add(15000,stop)
-
- if __name__ == '__main__':
- def test_sng ():
- w = gtk.Window()
- w.connect('delete-event', gtk.main_quit)
- t = SudokuNumberGrid(4)
- w.add(t)
- t.__entries__[(0,1)].set_color((0.0,1.0,0.0))
- t.__entries__[(0,1)].set_value(4)
- t.__entries__[(1,1)].set_error_highlight(True)
- t.__entries__[(1,1)].set_value(1)
- t.__entries__[(2,1)].set_color((0.0,0.0,1.0))
- t.__entries__[(2,1)].set_error_highlight(True)
- t.__entries__[(2,1)].set_value(2)
- t.__entries__[(3,1)].set_color((0.0,0.0,1.0))
- t.__entries__[(3,1)].set_error_highlight(True)
- t.__entries__[(3,1)].set_error_highlight(False)
- t.__entries__[(3,1)].set_value(3)
- t.__entries__[(3,1)].set_note_text('2,3,4','1,2')
- w.show_all()
- gtk.main()
-
- def reproduce_foobared_rendering ():
- from sudoku import SudokuGrid, sample_open_sudoku
- from dialog_swallower import SwappableArea
- sgd = SudokuGameDisplay()
- sgd.set_bg_color('black')
- w = gtk.Window()
- w.connect('delete-event', gtk.main_quit)
- vb = gtk.VBox()
- hb = gtk.HBox()
- swallower = SwappableArea(hb)
- tb = gtk.Toolbar()
- b = gtk.ToolButton(stock_id=gtk.STOCK_QUIT)
- b.connect('clicked',lambda x: w.hide() or gtk.main_quit())
- tb.add(b)
- def run_swallowed_dialog (*args):
- md = MessageDialog(title="Bar",label="Bar",sublabel="Baz "*12)
- swallower.run_dialog(md)
- b2 = gtk.ToolButton(label='Dialog')
- b2.connect('clicked',run_swallowed_dialog)
- tb.add(b2)
- vb.pack_start(tb,fill=False,expand=False)
- vb.pack_start(swallower,padding=12)
- w.add(vb)
- w.show_all()
- from gtk_goodies.dialog_extras import MessageDialog
- md = MessageDialog(title="Foo",label="Foo",sublabel="Bar "*12)
- swallower.run_dialog(md)
- hb.pack_start(sgd,padding=6)
- sgd.change_grid(SudokuGrid(sample_open_sudoku),9)
- gtk.main()
-
- def test_sudoku_game ():
- from sudoku import SudokuGrid, sample_open_sudoku
- sgd = SudokuGameDisplay(grid=SudokuGrid(sample_open_sudoku))
- sgd.set_bg_color('black')
- w = gtk.Window()
- w.connect('delete-event', gtk.main_quit)
- w.add(sgd)
- w.show_all()
- test_dance_grid(sgd)
- gtk.main()
-
- def test_number_selector ():
- w = gtk.Window()
- w.connect('delete-event',gtk.main_quit)
- ns = NumberSelector(default=3)
- def tell_me (b): print 'value->',b.get_value()
- ns.connect('changed',tell_me)
- w.add(ns)
- w.show_all()
- gtk.main()
-
- test_sudoku_game()
-
-